/*
 * @(#)HangulWizard.java    1.24 2000/10/03
 *
 * Copyright 2000-2001 by Jangho Hwang
 * Seoul, Korea 
 * All rights reserved.
 * 
 * 여러가지 한글관련 메소드를 제공하는 클래스이다.
 * 2000년 10월 3일 개천절날 너무도 할짓이 없기에, 
 * 아는 동생네 겜방에 가서 6시간동안 삽질하여 완성된
 * 이쁜 클래스이다. 유용하게 사용되었으면 좋겠다.
 */

package com.idstrust.wiseone.sg.wisehr.common.util;

import java.io.*;
import java.util.*;

/**
 * <font size=2 face=굴림체>
 * 한글을 가지고 여러가지 기능을 하는 class이다. <p>
 *
 * 개천절을 무료하게 보내기가 싫어서 만들었다. 기능은 아래와 같다-.-<p>
 *
 * <b>1</b>. 한글 한 문자(char)를 param으로 받아, 각 초성,중성,종성별로 
 * 분석하여 char[]형태로 리턴해주는 기능<p>
 *
 * <b>2</b>. 한글 문장(String)을 param으로 받아, 각 초성,중성,종성별로
 * 분석하여 String형태로 모두 리턴해주는 기능<p>
 *
 * <b>3</b>. 한글 한 문자(char)를 param으로 받아, 영어자판으로 놓고 입력한
 * 것처럼 영어문자들을 char[]형태로 리턴해주는 기능<p>
 *
 * <b>4</b>. 한글 문장(String)을 param으로 받아, 영어자판으로 놓고 입력한
 * 것처럼 String형태로 모두 리턴해주는 기능<p>
 *
 * <b>5</b>. 영어 char[]를 받아, 한글자판으로 놓고 입력한 것처럼 인식하고
 * 한글 한글자로 조합하여 char로 리턴해주는 기능<p>
 *
 * <b>6</b>. 한글을 실수로 영어자판으로 놓고 문장을 친것같은 String을 입력받아
 * 모든것을 한글로 한번에 조합하여 String으로 리턴해주는 기능<p>
 *
 * <b>7</b>. 입력받은 한글 char가 모음인지 자음인지 판별해주는 기능<p>
 *
 * <b>8</b>. 입력받은 한글 String을 발음나는 대로 영어 String으로 변환하여 
 * String으로 리턴해주는 기능 ( added at 2000/10/06 )
 *
 * </font>
 *
 * @author  Jangho Hwang, rath@cyberimagination.com
 * @version 1.24, 2000/10/04
 */
public final class HangulWizard {
    private static final int FIRST           = 0;
    private static final int SECOND          = 1;
    private static final int THIRD           = 2;

    private static Hashtable FIRST_TABLE     = new Hashtable();
    private static Hashtable FIRST_PROTABLE  = new Hashtable();
    private static Hashtable SECOND_TABLE    = new Hashtable();
    private static Hashtable SECOND_PROTABLE = new Hashtable();
    private static Hashtable THIRD_TABLE     = new Hashtable();
    private static Hashtable THIRD_PROTABLE  = new Hashtable();

    private static Hashtable VALID_TABLE     = new Hashtable();

    private static char[]    FIRST_CHAR;
    private static char[]    FIRST_MATCH;
    private static char[][]  FIRST_PRONUNCIATION;
    private static char[]    SECOND_CHAR;
    private static char[][]  SECOND_MATCH;
    private static char[][]  SECOND_PRONUNCIATION;
    private static char[][]  THIRD_CHAR;
    private static char[][]  THIRD_MATCH;
    private static char[][]  THIRD_PRONUNCIATION;

    static {
        FIRST_CHAR = new char[] { 'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ', 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ' };
        FIRST_MATCH = new char[] { 'r', 'R', 's', 'e', 'E', 'f', 'a', 'q', 'Q', 't', 'T', 'd', 'w', 'W', 'c', 'z', 'x', 'v', 'g' };
        FIRST_PRONUNCIATION = new char[][] { { 'k' }, { 'g', 'g' }, { 'n' }, { 'd' }, { 'd', 'd' }, { 'l' }, { 'm' }, { 'b' }, { 'b', 'b' }, { 's' },
            { 's', 's' }, {}, { 'j' }, { 'j', 'j' }, { 'c', 'h' }, { 'c' }, { 't' }, { 'p' }, { 'h' } };
        for (int i = 0; i < FIRST_CHAR.length; i++) {
            FIRST_TABLE.put(new Character(FIRST_MATCH[i]), new Character(FIRST_CHAR[i]));
            FIRST_PROTABLE.put(new Character(FIRST_CHAR[i]), FIRST_PRONUNCIATION[i]);
        }
        SECOND_CHAR = new char[] { 'ㅏ', 'ㅐ', 'ㅑ', 'ㅒ', 'ㅓ', 'ㅔ', 'ㅕ', 'ㅖ', 'ㅗ', 'ㅘ', 'ㅙ', 'ㅚ', 'ㅛ', 'ㅜ', 'ㅝ', 'ㅞ', 'ㅟ', 'ㅠ', 'ㅡ', 'ㅢ', 'ㅣ' };

        SECOND_MATCH = new char[][] { { 'k' }, { 'o' }, { 'i' }, { 'O' }, { 'j' }, { 'p' }, { 'u' }, { 'P' }, { 'h' }, { 'h', 'k' }, { 'h', 'o' },
            { 'h', 'l' }, { 'y' }, { 'n' }, { 'n', 'j' }, { 'n', 'p' }, { 'n', 'l' }, { 'b' }, { 'm' }, { 'm', 'l' }, { 'l' } };

        SECOND_PRONUNCIATION = new char[][] { { 'a' }, { 'a', 'e' }, { 'y', 'a' }, { 'y', 'a', 'e' }, { 'e', 'a' }, { 'e' }, { 'y', 'e', 'o' },
            { 'y', 'e', 'a' }, { 'o' }, { 'o', 'a' }, { 'o', 'a', 'e' }, { 'o', 'e' }, { 'y', 'o' }, { 'o', 'o' }, { 'u', 'e', 'a' }, { 'u', 'e' },
            { 'u', 'i' }, { 'y', 'o', 'o' }, { 'u' }, { 'u', 'i' }, { 'i' } };

        for (int i = 0; i < SECOND_CHAR.length; i++) {
            SECOND_TABLE.put(SECOND_MATCH[i], new Character(SECOND_CHAR[i]));
            SECOND_PROTABLE.put(new Character(SECOND_CHAR[i]), SECOND_PRONUNCIATION[i]);
        }
        THIRD_CHAR = new char[][] { {}, { 'ㄱ' }, { 'ㄱ', 'ㄱ' }, { 'ㄱ', 'ㅅ' }, { 'ㄴ' }, { 'ㄴ', 'ㅈ' }, { 'ㄴ', 'ㅎ' }, { 'ㄷ' }, { 'ㄹ' }, { 'ㄹ', 'ㄱ' },
            { 'ㄹ', 'ㅁ' }, { 'ㄹ', 'ㅂ' }, { 'ㄹ', 'ㅅ' }, { 'ㄹ', 'ㅌ' }, { 'ㄹ', 'ㅍ' }, { 'ㄹ', 'ㅎ' }, { 'ㅁ' }, { 'ㅂ' }, { 'ㅂ', 'ㅅ' }, { 'ㅅ' },
            { 'ㅅ', 'ㅅ' }, { 'ㅇ' }, { 'ㅈ' }, { 'ㅊ' }, { 'ㅋ' }, { 'ㅌ' }, { 'ㅍ' }, { 'ㅎ' } };
        THIRD_MATCH = new char[][] { {}, { 'r' }, { 'r', 'r' }, { 'r', 't' }, { 's' }, { 's', 'w' }, { 's', 'g' }, { 'e' }, { 'f' }, { 'f', 'r' },
            { 'f', 'a' }, { 'f', 'q' }, { 'f', 't' }, { 'f', 'x' }, { 'f', 'v' }, { 'f', 'g' }, { 'a' }, { 'q' }, { 'q', 't' }, { 't' }, { 'T' },
            { 'd' }, { 'w' }, { 'c' }, { 'z' }, { 'x' }, { 'v' }, { 'g' } };
        THIRD_PRONUNCIATION = new char[][] { {}, { 'k' }, { 'g', 'g' }, { 'k' }, { 'n' }, { 'n' }, { 'n' }, { 'd' }, { 'l' }, { 'l' }, { 'm' },
            { 'b' }, { 'l' }, { 'l' }, { 'b' }, { 'l' }, { 'm' }, { 'b' }, { 'b' }, { 'd' }, { 'd' }, { 'n', 'g' }, { 'k' }, { 'k' }, { 'k' },
            { 'k' }, { 'k' }, { 'k' } };

        for (int i = 0; i < THIRD_CHAR.length; i++) {
            THIRD_TABLE.put(THIRD_MATCH[i], THIRD_CHAR[i]);
            THIRD_PROTABLE.put(THIRD_CHAR[i], THIRD_PRONUNCIATION[i]);
        }

        char[] VALID_CHARS = new char[] { 'q', 'w', 'e', 'r', 't', 'a', 's', 'd', 'f', 'g', 'z', 'x', 'c', 'v', 'y', 'h', 'b', 'n', 'u', 'j', 'm',
            'i', 'k', 'o', 'O', 'l', 'p', 'P', 'R', 'E', 'Q', 'T', 'W' };

        for (int i = 0; i < VALID_CHARS.length; i++) {
            Character ch = new Character(VALID_CHARS[i]);
            VALID_TABLE.put(ch, ch);
        }

    }

    private boolean          isSingleParsing;

    /**
     * HangulWizard class의 생성자입니다. 생성자에서 특별히 
     * 처리되는 일은 없으며, 단독 자음/모음에 대한 파싱이 
     * Default인 false로 설정된다.
     */
    public HangulWizard() {
        isSingleParsing = false;
    }

    /**
     * HangulWizard class의 생성자입니다. 생성자에서 특별히 
     * 처리되는 일은 없으며, 단독 자음/모음에 대한 파싱은 
     * 지정된 isSnigleParsing으로 설정된다.
     *
     * @param isSingleParsing 단일문자에 대한 파싱 여부
     */
    public HangulWizard(boolean isSingleParsing) {
        this.isSingleParsing = isSingleParsing;
    }

    /**
     * 한글로 return시, 'ㄱ', 'ㅓ' 같은 단일형태소의 character를 
     * 그대로 'ㄱ' 형태로 표시할 것인지, 'r' 로 할것인지 결정해준다.
     * true일 경우 'ㄱ'로 반환된다.
     *
     * @param singleParse 단일문자에 대한 파싱 여부
     */
    public void setSingleParsing(boolean singleParse) {
        isSingleParsing = singleParse;
    }

    /**
     * 한글로 return시, 'ㄱ', 'ㅓ' 같은 단일형태소의 character를 
     * 그대로 'ㄱ' 형태로 표시할 것인지, 'r' 로 하는지 알려준다.
     *
     * @return 단일문자에 대한 파싱 여부
     */
    public boolean isSingleParsing() {
        return isSingleParsing;
    }

    /**
     * 입력받은 en_str를 한글모드로 모두 변환하여 return해준다, 한글 형태로소써
     * 부적합한 문자들은 en_str로 받은 문자 그대로 처리하여 주며,
     * 단일 형태소 'ㄱ', 'ㅗ' 같은것들은 isSingleParsing()이 true이면 'ㄱ'으로
     * 처리되며, false이면 'r' 로 처리된다.
     * 
     * @param en_str 한글을 영어자판으로 입력한 String
     *
     * @return 한글로 조합한 String
     */
    public String getKorean(String en_str) {
        CharArrayWriter caw = new CharArrayWriter(en_str.length() >> 1);
        int len = en_str.length();
        int offset = 0;

        try {
            while (offset < len) {
                if (offset >= len)
                    break;
                char ch1 = en_str.charAt(offset);
                if (VALID_TABLE.get(new Character(ch1)) == null) {
                    caw.write(ch1);
                    offset++;
                    continue;
                }

                if (!isJaum(ch1)) {
                    caw.write(getKoreanElementAtEnglishElement(new char[] { ch1 }, SECOND)[0]);
                    offset++;
                    continue;
                }

                if (offset + 1 >= len) {
                    caw.write(getKoreanElementAtEnglishElement(new char[] { ch1 }, FIRST)[0]);
                    break;
                }
                char ch2 = en_str.charAt(offset + 1);
                if (VALID_TABLE.get(new Character(ch2)) == null) {
                    caw.write(getKoreanElementAtEnglishElement(new char[] { ch1 }, FIRST)[0]);
                    caw.write(ch2);
                    offset += 2;
                    continue;
                }

                if (isJaum(ch2)) {
                    caw.write(((Character) FIRST_TABLE.get(new Character(ch2))).charValue());
                    offset++;
                    continue;
                }

                if (offset + 2 >= len) {
                    caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2 }));
                    break;
                }
                char ch3 = en_str.charAt(offset + 2);
                if (VALID_TABLE.get(new Character(ch3)) == null) {
                    caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2 }));
                    caw.write(ch3);
                    offset += 3;
                    continue;
                }

                boolean isDoubleMoum = false;
                boolean is3Jaum = isJaum(ch3);

                if (isDoubleMoum(ch2, ch3)) {
                    isDoubleMoum = true;
                } else {
                    /* '가' 같은 것이 나오고 또 모음이 나왔을때, '가ㅔ' 이런식으로 출력해준다 */
                    if (!is3Jaum) {
                        caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2 }));
                        caw.write(getKoreanElementAtEnglishElement(new char[] { ch3 }, SECOND)[0]);
                        offset += 3;
                        continue;
                    }

                    isDoubleMoum = false;
                }

                if (offset + 3 >= len) {
                    /* 여기가 문자의 끝이라면 */
                    caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3 }));
                    break;
                }
                char ch4 = en_str.charAt(offset + 3);
                if (VALID_TABLE.get(new Character(ch4)) == null) {
                    caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3 }));
                    caw.write(ch4);
                    offset += 4;
                    continue;
                }

                /* 
                    isDoubleMoum이고, 
                        ch4가 모음이면 ch4는 단독모음이다. 그러므로 트랙잭션 통과.
                        ch4가 자음이면 어떻게 될지 모르므로 통과,
                */
                boolean is4Jaum = isJaum(ch4);
                if (is3Jaum && !is4Jaum) {
                    caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2 }));
                    offset += 2;
                    continue;
                }

                if (isDoubleMoum && !is4Jaum) {
                    caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3 }));
                    caw.write(getKoreanElementAtEnglishElement(new char[] { ch4 }, SECOND));
                    offset += 4;
                    continue;
                }

                /* 5번째 ch5의 시작부분 */
                if (offset + 4 >= len) {
                    if (isDoubleMoum) {
                        caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3, ch4 }));
                    } else {
                        if (isThirdJaum(ch3, ch4)) {
                            caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3, ch4 }));
                        } else {
                            if (is4Jaum) {
                                caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3 }));
                                caw.write(getKoreanElementAtEnglishElement(new char[] { ch4 }, FIRST));
                            } else {
                                caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2 }));
                                caw.write(getKoreanWordAtEnglishElements(new char[] { ch3, ch4 }));
                            }
                            break;
                        }
                    }
                    break;
                }
                char ch5 = en_str.charAt(offset + 4);
                if (VALID_TABLE.get(new Character(ch5)) == null) {
                    /*
                        ch5가 올바르지 않은 문자라면 ch1,ch2,ch3,ch4을 어떻게든 출력해버린다.
                    */
                    if (isDoubleMoum) {
                        caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3, ch4 }));
                    } else {
                        if (isThirdJaum(ch3, ch4)) {

                            caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3, ch4 }));
                        } else {
                            if (is4Jaum) {
                                caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3 }));
                                caw.write(getKoreanElementAtEnglishElement(new char[] { ch4 }, FIRST));
                            } else {
                                caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2 }));
                                caw.write(getKoreanWordAtEnglishElements(new char[] { ch3, ch4 }));
                            }
                        }
                    }
                    caw.write(ch5);
                    offset += 5;
                    continue;
                }

                boolean is5Jaum = isJaum(ch5);

                if (!isDoubleMoum && ((is5Jaum && !isThirdJaum(ch3, ch4)) || !is5Jaum)) {
                    caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3 }));
                    offset += 3;
                    continue;
                }

                /* 쌍모음과 쌍종성에 대한 처리를 모두 해주어야 한다 */
                if (isDoubleMoum) {
                    if (is4Jaum && !is5Jaum) {
                        caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3 }));
                        offset += 3;
                        continue;
                    }

                    if (offset + 5 < len) // 6번째 글짜가 존재한다면
                    {
                        char ch6 = en_str.charAt(offset + 5);
                        boolean is6Jaum = isJaum(ch6);
                        if (is6Jaum) {
                            caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3, ch4, ch5 }));
                            offset += 5;
                            continue;
                        }
                        caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3, ch4 }));
                    } else {
                        caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3, ch4 }));
                        caw.write(getKoreanElementAtEnglishElement(new char[] { ch5 }, FIRST));
                        offset += 5;
                        continue;
                    }
                } else {
                    if (isThirdJaum(ch3, ch4)) {
                        caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3, ch4 }));
                    } else {
                        if (is4Jaum) {
                            caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2, ch3 }));
                            caw.write(getKoreanElementAtEnglishElement(new char[] { ch4 }, FIRST));
                        } else {
                            caw.write(getKoreanWordAtEnglishElements(new char[] { ch1, ch2 }));
                            caw.write(getKoreanWordAtEnglishElements(new char[] { ch3, ch4 }));
                        }
                    }
                }
                offset += 4;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return new String(caw.toCharArray());
    }

    /**
     * 입력받은 한글 String을 발음나는 대로의 
     * 영문 String으로 변환해준다.
     *
     * @param korean 한글로 이루어진 문장
     *
     * @return 발음나는대로 번역된 영문장
     */
    public String getEnglishPronunciation(String korean) {
        if (korean == null)
            return null;
        int len = korean.length();

        StringBuffer sb = new StringBuffer(16);
        boolean isBeforeHangul = false;
        for (int i = 0; i < len; i++) {
            char[] chars = getElements(korean.charAt(i));
            if (chars.length == 1) {
                isBeforeHangul = false;
                sb.append(chars[0]);
                continue;
            }

            StringBuffer s = new StringBuffer(4);
            char[] ch1 = (char[]) FIRST_PROTABLE.get(new Character(chars[0]));
            if (ch1.length != 0)
                s.append(ch1);
            char[] ch2 = (char[]) SECOND_PROTABLE.get(new Character(chars[1]));
            if (ch2.length != 0)
                s.append(ch2);

            if (chars.length == 3) {
                int index = getOrderAtKoreanTable(new char[] { chars[2] }, THIRD);
                char[] ch3 = THIRD_PRONUNCIATION[index];
                if (ch3.length != 0)
                    s.append(ch3);
            }

            if (chars.length == 4) {
                int index = getOrderAtKoreanTable(new char[] { chars[2], chars[3] }, THIRD);
                char[] ch3 = THIRD_PRONUNCIATION[index];
                if (ch3.length != 0)
                    s.append(ch3);
            }

            if (isBeforeHangul) {
                sb.append('-');
                sb.append(s);
            } else {
                s.setCharAt(0, (char) ((int) s.charAt(0) - 32));
                sb.append(s);
            }
            isBeforeHangul = true;
        }

        return sb.toString();
    }

    /**
     * chs배열의 영문자들을 단일 한글 한글자로 분석해준다.
     * 예를 들어 'r', 'k' 가 오면 '가'로 분석해주며, 
     * 'r', 'k', 'r' 가 오면 '각'으로, 'r', 'h', 'o', 's' 가 오면
     * '괜'으로 분석해준다. 
     * 만약 chs배열의 길이가 2보다 작거나, 5보다 크거나. 조합할 수 없는 
     * 한글문자라면 '?'를 반환해준다.
     *
     * @param chs 영문자 조합 (ex. 'd', 'k', 's') 
     *
     * @return 조합을 합친 한글 한글자. 조합이 불가능한 경우 '?'를 반환한다.
     */
    public char getKoreanWordAtEnglishElements(char[] chs) {
        if (chs.length == 2) {
            int firstOrder = getOrderAtTable(new char[] { chs[0] }, FIRST);
            int secondOrder = getOrderAtTable(new char[] { chs[1] }, SECOND);
            return (char) (44032 + (firstOrder * 588) + (secondOrder * 28));
        }

        if (chs.length == 3) {
            if (isJaum(chs[2])) {
                int firstOrder = getOrderAtTable(new char[] { chs[0] }, FIRST);
                int secondOrder = getOrderAtTable(new char[] { chs[1] }, SECOND);
                int thirdOrder = getOrderAtTable(new char[] { chs[2] }, THIRD);
                return (char) (44032 + (firstOrder * 588) + (secondOrder * 28) + thirdOrder);
            } else {
                int firstOrder = getOrderAtTable(new char[] { chs[0] }, FIRST);
                int secondOrder = getOrderAtTable(new char[] { chs[1], chs[2] }, SECOND);
                return (char) (44032 + (firstOrder * 588) + (secondOrder * 28));
            }
        }

        if (chs.length == 4) {
            if (isDoubleMoum(chs[1], chs[2])) {
                int firstOrder = getOrderAtTable(new char[] { chs[0] }, FIRST);
                int secondOrder = getOrderAtTable(new char[] { chs[1], chs[2] }, SECOND);
                int thirdOrder = getOrderAtTable(new char[] { chs[3] }, THIRD);
                return (char) (44032 + (firstOrder * 588) + (secondOrder * 28) + thirdOrder);
            } else {
                int firstOrder = getOrderAtTable(new char[] { chs[0] }, FIRST);
                int secondOrder = getOrderAtTable(new char[] { chs[1] }, SECOND);
                int thirdOrder = getOrderAtTable(new char[] { chs[2], chs[3] }, THIRD);
                return (char) (44032 + (firstOrder * 588) + (secondOrder * 28) + thirdOrder);
            }
        }

        if (chs.length == 5) {
            int firstOrder = getOrderAtTable(new char[] { chs[0] }, FIRST);
            int secondOrder = getOrderAtTable(new char[] { chs[1], chs[2] }, SECOND);
            int thirdOrder = getOrderAtTable(new char[] { chs[3], chs[4] }, THIRD);
            return (char) (44032 + (firstOrder * 588) + (secondOrder * 28) + thirdOrder);
        }

        return '?';
    }

    /**
     * 해당 영문자 ch가 자음인지 구별해준다.
     *
     * @param ch 한글자판이라는 가정하에 int로 cast된 영문자
     *
     * @return <code>true</code> 한글 형태소로 바꾸었을때, 자음일 경우;
     *          <code>false</code> 자음이 아닌 경우.
     */
    public boolean isJaum(int ch) {
        return isJaum((char) ch);
    }

    /**
     * 해당 영문자 ch가 자음인지 구별해준다.
     *
     * @param ch 한글자판이라는 가정하의 영문자
     *
     * @return <code>true</code> 한글 형태소로 바꾸었을때, 자음일 경우;
     *          <code>false</code> 자음이 아닌 경우.
     */
    public boolean isJaum(char ch) {
        if (FIRST_TABLE.get(new Character(ch)) == null)
            return false;
        return true;
    }

    /**
     * parameter로 받은 두개의 char가 쌍모음(?)인지 구별해준다.
     * 예를 들어, 'ㅗ', 'ㅐ' 이면 true이를 반환하며, 
     * 'ㄱ', 'ㅏ' 거나, 'ㅓ', 'ㄱ' 따위일 경우 false,
     * 물론 둘다 자음이거나, 조합할 수 없는 두개의 모음이라도 false를 
     * 반환한다.
     *
     * @param first 쌍모음 중 첫번째 모음
     * @param second 쌍모음 중 두번째 모음
     *
     * @return <code>true</code> first, second가 조합가능한 쌍모음일 경우;
     *          <code>false</code> first, second가 조합가능한 쌍모음이 아닌 경우.
     */
    private boolean isDoubleMoum(char first, char second) {
        for (int i = 0; i < SECOND_MATCH.length; i++)
            if (SECOND_MATCH[i][0] == first && SECOND_MATCH[i].length == 2 && second == SECOND_MATCH[i][1])
                return true;
        return false;
    }

    /**
     * parameter로 받은 두개의 char가 종성으로 적합한지 구별해준다.
     * 예를 들면, 'ㄴ', 'ㅎ', 같은 것은 true를 반환하겠지만,
     * 'ㅂ', 'ㄱ' 같은것은 종성으로 부적합하므로 false를 반환한다.
     *
     * @param first 쌍자음 중 첫번째 자음
     * @param second 쌍자음 중 두번째 자음
     *
     * @return <code>true</code> first, second가 조합가능한 쌍자음일 경우;
     *          <code>false</code> first, second가 조합가능한 쌍자음이 아닌 경우.
     */
    private boolean isThirdJaum(char first, char second) {
        for (int i = 0; i < THIRD_MATCH.length; i++)
            if (THIRD_MATCH[i].length > 0 && THIRD_MATCH[i][0] == first && THIRD_MATCH[i].length == 2 && second == THIRD_MATCH[i][1])
                return true;
        return false;
    }

    /**
     * parameter로 받은 한글 str을 각각 형태소별로 구분하여 String형태로
     * 반환해준다. 
     * 예를 들어, '안녕하세요 오랜만입니다'를 parameter로 넘겨준다면,
     * 'ㅇㅏㄴㄴㅕㅇㅎㅏㅅㅔㅇㅛ ㅇㅗㄹㅐㄴㅁㅏㄴㅇㅣㅂㄴㅣㄷㅏ'가 반환된다.
     *
     * @param str 형태소별로 분석하고자하는 String 
     *
     * @return 형태소별로 분석된 한글 String 
     *
     */
    public String getElementsAtString(String str) {
        return getElementsAtString(str, false);
    }

    /**
     * parameter로 받은 한글 str을 각각 형태소별로 구분하여 String형태로
     * 반환해주는데, toEnglish가 true라면 형태소별로 구분된 것을 모두 영문으로 
     * 변환하여 준다. 
     * 예를 들어, toEnglish가 false라면 
     * '안녕하세요 오랜만입니다'를 parameter로 넘겨준다면,
     * 'ㅇㅏㄴㄴㅕㅇㅎㅏㅅㅔㅇㅛ ㅇㅗㄹㅐㄴㅁㅏㄴㅇㅣㅂㄴㅣㄷㅏ'가 반환된다.
     * 그러나 toEnglish가 true라면
     * 'dkssudgktpdy dhfosaksdlqslek'가 반환된다.
     *
     * @param str 형태소별로 분석하고자하는 String 
     * @param toEnglish 분석한 결과를 한글자판형식으로 할것인지, 영문자판 
     *                  형식으로 할 것인지의 여부.
     *
     * @return 형태소별로 분석한 후, toEnglish 자판형식으로 변형된 String
     *
     */
    public String getElementsAtString(String str, boolean toEnglish) {
        int len = str.length();

        CharArrayWriter caw = new CharArrayWriter(len);

        for (int i = 0; i < len; i++) {
            char[] chars = null;
            if (toEnglish)
                chars = getEnglishElements(str.charAt(i));
            else
                chars = getElements(str.charAt(i));
            try {
                caw.write(chars);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return new String(caw.toCharArray());
    }

    /** 
     * parameter로 받은 ch를 형태소로 분리하여 char[]형태로 반환해준다.
     * ch가 '행'이 였다면 length가 3인 char array인 { 'ㅎ', 'ㅐ', 'ㅇ' }
     * 을인 char array를 생성하여 반환해준다. 
     * 만약, 한글이 아닌 것을 parameter로 넘겼다면 넘긴 그대로의 ch를
     * length가 1인 char array로 반환해준다.
     *
     * @param ch 형태소별로 분석하고자 하는 한글 한글자.
     *
     * @return char[] 각 형태소별로 분석된 한글 형태소별 char[].
     */
    public char[] getElements(char ch) {
        return getElements((int) ch);
    }

    /** 
     * parameter로 받은 ch를 형태소로 분리하여 char[]형태로 반환해준다.
     * ch가 '행'이 였다면 length가 3인 char array인 { 'ㅎ', 'ㅐ', 'ㅇ' }
     * 을인 char array를 생성하여 반환해준다. 
     * 만약, 한글이 아닌 것을 parameter로 넘겼다면 넘긴 그대로의 ch를
     * length가 1인 char array로 반환해준다.
     *
     * @param ch 형태소별로 분석하고자 하는 한글 한글자.
     *
     * @return char[] 각 형태소별로 분석된 한글 형태소별 char[].
     */
    public char[] getElements(int ch) {
        if (ch < 44032 || ch > 55203)
            return new char[] { (char) ch };
        ch = ch - 44032;
        int first = ch / 588;
        int temp = ch % 588;
        int second = temp / 28;
        int third = temp % 28;
        char[] returnChar = new char[2 + THIRD_CHAR[third].length];
        returnChar[0] = FIRST_CHAR[first];
        returnChar[1] = SECOND_CHAR[second];
        System.arraycopy(THIRD_CHAR[third], 0, returnChar, 2, THIRD_CHAR[third].length);
        return returnChar;
    }

    /** 
     * parameter로 받은 ch를 형태소로 분리하여 char[]형태로 반환해준다.
     * ch가 '행'이 였다면 length가 3인 char array인 { 'g', 'o', 'd' }
     * 을인 char array를 생성하여 반환해준다. 
     * 만약, 한글이 아닌 것을 parameter로 넘겼다면 넘긴 그대로의 ch를
     * length가 1인 char array로 반환해준다.
     *
     * @param ch 형태소별로 분석하고자 하는 한글 한글자.
     *
     * @return char[] 각 형태소별로 분석된 영문 형태소별 char[].
     */
    public char[] getEnglishElements(char ch) {
        return getEnglishElements((int) ch);
    }

    /** 
     * parameter로 받은 ch를 형태소로 분리하여 char[]형태로 반환해준다.
     * ch가 '행'이 였다면 length가 3인 char array인 { 'g', 'o', 'd' }
     * 을인 char array를 생성하여 반환해준다. 
     * 만약, 한글이 아닌 것을 parameter로 넘겼다면 넘긴 그대로의 ch를
     * length가 1인 char array로 반환해준다.
     *
     * @param ch 형태소별로 분석하고자 하는 한글 한글자.
     *
     * @return char[] 각 형태소별로 분석된 영문 형태소별 char[].
     */
    public char[] getEnglishElements(int ch) {
        if (ch < 44032 || ch > 55203)
            return new char[] { (char) ch };
        ch = ch - 44032;
        int first = ch / 588;
        int temp = ch % 588;
        int second = temp / 28;
        int third = temp % 28;
        char[] returnChar = new char[1 + SECOND_MATCH[second].length + THIRD_MATCH[third].length];
        returnChar[0] = FIRST_MATCH[first];
        System.arraycopy(SECOND_MATCH[second], 0, returnChar, 1, SECOND_MATCH[second].length);
        System.arraycopy(THIRD_MATCH[third], 0, returnChar, 1 + SECOND_MATCH[second].length, THIRD_MATCH[third].length);
        return returnChar;
    }

    /**
     * 각 영문자 형태소 char[]를 한글 형태소 char[]로 변환해준다. 
     *
     * @param chs 영문자 형태소 char[]
     * @param code <code>FIRST</code> 분석할 형태소의 종류가 초성;
     *              <code>SECOND</code> 분석할 형태소의 종류가 중성;
     *              <code>THIRD</code> 분석할 형태소의 종류가 종성;
     *
     * @return 분석된 한글 형태소 
     */
    private char[] getKoreanElementAtEnglishElement(char[] chs, int code) {
        if (!isSingleParsing)
            return chs;

        int order = getOrderAtTable(chs, code);

        if (order == -1)
            return new char[] { '?' };

        switch (code) {
        case FIRST:
            return new char[] { FIRST_CHAR[order] };
        case SECOND:
            return new char[] { SECOND_CHAR[order] };
        case THIRD:
            return THIRD_CHAR[order];
        }
        return new char[] { '?' };
    }

    /**
     * 내부 초성, 중성, 종성의 TABLE에서 chs의 index를 구해준다.
     *
     * @param chs 각 분석하고자 하는 영문 형태소의 char[] 
     *
     * @param code <code>FIRST</code> 찾을 영문 형태소의 종류가 초성;
     *              <code>SECOND</code> 찾을 영문 형태소의 종류가 중성;
     *              <code>THIRD</code> 찾을 영문 형태소의 종류가 종성;
     *
     * @return 각 TABLE에서 찾을 영문 형태소의 index
     *
     */
    private int getOrderAtTable(char[] chs, int code) {
        switch (code) {
        case FIRST:
            for (int i = 0; i < FIRST_MATCH.length; i++)
                if (FIRST_MATCH[i] == chs[0])
                    return i;
            break;

        case SECOND:
            for (int i = 0; i < SECOND_MATCH.length; i++) {
                if (SECOND_MATCH[i][0] == chs[0]) {
                    if (chs.length == 2) {
                        if (SECOND_MATCH[i].length == 2 && chs[1] == SECOND_MATCH[i][1])
                            return i;
                    } else
                        return i;
                }
            }
            break;

        case THIRD:
            for (int i = 1; i < THIRD_MATCH.length; i++) {
                if (THIRD_MATCH[i][0] == chs[0]) {
                    if (chs.length == 2) {
                        if (THIRD_MATCH[i].length == 2 && chs[1] == THIRD_MATCH[i][1])
                            return i;
                    } else
                        return i;
                }
            }
            break;
        }

        return -1;
    }

    /**
     * 내부 초성, 중성, 종성의 TABLE에서 chs의 index를 구해준다.
     *
     * @param chs 각 분석하고자 하는 한글 형태소의 char[] 
     *
     * @param code <code>FIRST</code> 찾을 한글 형태소의 종류가 초성;
     *              <code>SECOND</code> 찾을 한글 형태소의 종류가 중성;
     *              <code>THIRD</code> 찾을 한글 형태소의 종류가 종성;
     *
     * @return 각 TABLE에서 찾을 한글 형태소의 index
     *
     */
    private int getOrderAtKoreanTable(char[] chs, int code) {
        switch (code) {
        case FIRST:
            for (int i = 0; i < FIRST_CHAR.length; i++)
                if (FIRST_CHAR[i] == chs[0])
                    return i;
            break;

        case SECOND:
            for (int i = 0; i < SECOND_CHAR.length; i++)
                if (SECOND_CHAR[i] == chs[0])
                    return i;
            break;

        case THIRD:
            for (int i = 1; i < THIRD_CHAR.length; i++) {
                if (THIRD_CHAR[i][0] == chs[0]) {
                    if (chs.length == 2) {
                        if (THIRD_CHAR[i].length == 2 && chs[1] == THIRD_CHAR[i][1])
                            return i;
                    } else
                        return i;
                }
            }
            break;
        }

        return -1;
    }

    public static void main(String[] args) {
        HangulWizard hw = new HangulWizard();
        String kor = hw.getKorean("dks sudgktpdy");
        System.out.println(kor);
    }
}
